home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / lib / ruby / 1.8 / fileutils.rb < prev    next >
Encoding:
Ruby Source  |  2006-02-17  |  41.2 KB  |  1,575 lines

  1. # = fileutils.rb
  2. # Copyright (c) 2000-2005 Minero Aoki <aamine@loveruby.net>
  3. # This program is free software.
  4. # You can distribute/modify this program under the same terms of ruby.
  5. # == module FileUtils
  6. # Namespace for several file utility methods for copying, moving, removing, etc.
  7. # === Module Functions
  8. #   cd(dir, options)
  9. #   cd(dir, options) {|dir| .... }
  10. #   pwd()
  11. #   mkdir(dir, options)
  12. #   mkdir(list, options)
  13. #   mkdir_p(dir, options)
  14. #   mkdir_p(list, options)
  15. #   rmdir(dir, options)
  16. #   rmdir(list, options)
  17. #   ln(old, new, options)
  18. #   ln(list, destdir, options)
  19. #   ln_s(old, new, options)
  20. #   ln_s(list, destdir, options)
  21. #   ln_sf(src, dest, options)
  22. #   cp(src, dest, options)
  23. #   cp(list, dir, options)
  24. #   cp_r(src, dest, options)
  25. #   cp_r(list, dir, options)
  26. #   mv(src, dest, options)
  27. #   mv(list, dir, options)
  28. #   rm(list, options)
  29. #   rm_r(list, options)
  30. #   rm_rf(list, options)
  31. #   install(src, dest, mode = <src's>, options)
  32. #   chmod(mode, list, options)
  33. #   chmod_R(mode, list, options)
  34. #   chown(user, group, list, options)
  35. #   chown_R(user, group, list, options)
  36. #   touch(list, options)
  37. #
  38. # The <tt>options</tt> parameter is a hash of options, taken from the list
  39. # <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and <tt>:verbose</tt>.
  40. # <tt>:noop</tt> means that no changes are made.  The other two are obvious.
  41. # Each method documents the options that it honours.
  42. #
  43. # All methods that have the concept of a "source" file or directory can take
  44. # either one file or a list of files in that argument.  See the method
  45. # documentation for examples.
  46. #
  47. # There are some `low level' methods, which do not accept any option:
  48. #
  49. #   copy_entry(src, dest, preserve = false, dereference = false)
  50. #   copy_file(src, dest, preserve = false, dereference = true)
  51. #   copy_stream(srcstream, deststream)
  52. #   remove_entry(path, force = false)
  53. #   remove_entry_secure(path, force = false)
  54. #   remove_file(path, force = false)
  55. #   compare_file(path_a, path_b)
  56. #   compare_stream(stream_a, stream_b)
  57. #   uptodate?(file, cmp_list)
  58. #
  59. # == module FileUtils::Verbose
  60. # This module has all methods of FileUtils module, but it outputs messages
  61. # before acting.  This equates to passing the <tt>:verbose</tt> flag to methods
  62. # in FileUtils.
  63. # == module FileUtils::NoWrite
  64. # This module has all methods of FileUtils module, but never changes
  65. # files/directories.  This equates to passing the <tt>:noop</tt> flag to methods
  66. # in FileUtils.
  67. # == module FileUtils::DryRun
  68. # This module has all methods of FileUtils module, but never changes
  69. # files/directories.  This equates to passing the <tt>:noop</tt> and
  70. # <tt>:verbose</tt> flags to methods in FileUtils.
  71.  
  72. module FileUtils
  73.  
  74.   def self.private_module_function(name)   #:nodoc:
  75.     module_function name
  76.     private_class_method name
  77.   end
  78.  
  79.   # This hash table holds command options.
  80.   OPT_TABLE = {}   #:nodoc: internal use only
  81.  
  82.   #
  83.   # Options: (none)
  84.   #
  85.   # Returns the name of the current directory.
  86.   #
  87.   def pwd
  88.     Dir.pwd
  89.   end
  90.   module_function :pwd
  91.  
  92.   alias getwd pwd
  93.   module_function :getwd
  94.  
  95.   #
  96.   # Options: verbose
  97.   # 
  98.   # Changes the current directory to the directory +dir+.
  99.   # 
  100.   # If this method is called with block, resumes to the old
  101.   # working directory after the block execution finished.
  102.   # 
  103.   #   FileUtils.cd('/', :verbose => true)   # chdir and report it
  104.   # 
  105.   def cd(dir, options = {}, &block) # :yield: dir
  106.     fu_check_options options, :verbose
  107.     fu_output_message "cd #{dir}" if options[:verbose]
  108.     Dir.chdir(dir, &block)
  109.     fu_output_message 'cd -' if options[:verbose] and block
  110.   end
  111.   module_function :cd
  112.  
  113.   alias chdir cd
  114.   module_function :chdir
  115.  
  116.   OPT_TABLE['cd']    =
  117.   OPT_TABLE['chdir'] = %w( verbose )
  118.  
  119.   #
  120.   # Options: (none)
  121.   # 
  122.   # Returns true if +newer+ is newer than all +old_list+.
  123.   # Non-existent files are older than any file.
  124.   # 
  125.   #   FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
  126.   #       system 'make hello.o'
  127.   # 
  128.   def uptodate?(new, old_list, options = nil)
  129.     raise ArgumentError, 'uptodate? does not accept any option' if options
  130.  
  131.     return false unless File.exist?(new)
  132.     new_time = File.mtime(new)
  133.     old_list.each do |old|
  134.       if File.exist?(old)
  135.         return false unless new_time > File.mtime(old)
  136.       end
  137.     end
  138.     true
  139.   end
  140.   module_function :uptodate?
  141.  
  142.   #
  143.   # Options: mode noop verbose
  144.   # 
  145.   # Creates one or more directories.
  146.   # 
  147.   #   FileUtils.mkdir 'test'
  148.   #   FileUtils.mkdir %w( tmp data )
  149.   #   FileUtils.mkdir 'notexist', :noop => true  # Does not really create.
  150.   #   FileUtils.mkdir 'tmp', :mode => 0700
  151.   # 
  152.   def mkdir(list, options = {})
  153.     fu_check_options options, :mode, :noop, :verbose
  154.     list = fu_list(list)
  155.     fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
  156.     return if options[:noop]
  157.  
  158.     list.each do |dir|
  159.       fu_mkdir dir, options[:mode]
  160.     end
  161.   end
  162.   module_function :mkdir
  163.  
  164.   OPT_TABLE['mkdir'] = %w( noop verbose mode )
  165.  
  166.   #
  167.   # Options: mode noop verbose
  168.   # 
  169.   # Creates a directory and all its parent directories.
  170.   # For example,
  171.   # 
  172.   #   FileUtils.mkdir_p '/usr/local/lib/ruby'
  173.   # 
  174.   # causes to make following directories, if it does not exist.
  175.   #     * /usr
  176.   #     * /usr/local
  177.   #     * /usr/local/lib
  178.   #     * /usr/local/lib/ruby
  179.   #
  180.   # You can pass several directories at a time in a list.
  181.   # 
  182.   def mkdir_p(list, options = {})
  183.     fu_check_options options, :mode, :noop, :verbose
  184.     list = fu_list(list)
  185.     fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
  186.     return *list if options[:noop]
  187.  
  188.     list.map {|path| path.sub(%r</\z>, '') }.each do |path|
  189.       # optimize for the most common case
  190.       begin
  191.         fu_mkdir path, options[:mode]
  192.         next
  193.       rescue SystemCallError
  194.         next if File.directory?(path)
  195.       end
  196.  
  197.       stack = []
  198.       until path == stack.last   # dirname("/")=="/", dirname("C:/")=="C:/"
  199.         stack.push path
  200.         path = File.dirname(path)
  201.       end
  202.       stack.reverse_each do |path|
  203.         begin
  204.           fu_mkdir path, options[:mode]
  205.         rescue SystemCallError => err
  206.           raise unless File.directory?(path)
  207.         end
  208.       end
  209.     end
  210.  
  211.     return *list
  212.   end
  213.   module_function :mkdir_p
  214.  
  215.   alias mkpath    mkdir_p
  216.   alias makedirs  mkdir_p
  217.   module_function :mkpath
  218.   module_function :makedirs
  219.  
  220.   OPT_TABLE['mkdir_p']  =
  221.   OPT_TABLE['mkpath']   =
  222.   OPT_TABLE['makedirs'] = %w( noop verbose )
  223.  
  224.   def fu_mkdir(path, mode)   #:nodoc:
  225.     path = path.sub(%r</\z>, '')
  226.     if mode
  227.       Dir.mkdir path, mode
  228.       File.chmod mode, path
  229.     else
  230.       Dir.mkdir path
  231.     end
  232.   end
  233.   private_module_function :fu_mkdir
  234.  
  235.   #
  236.   # Options: noop, verbose
  237.   # 
  238.   # Removes one or more directories.
  239.   # 
  240.   #   FileUtils.rmdir 'somedir'
  241.   #   FileUtils.rmdir %w(somedir anydir otherdir)
  242.   #   # Does not really remove directory; outputs message.
  243.   #   FileUtils.rmdir 'somedir', :verbose => true, :noop => true
  244.   # 
  245.   def rmdir(list, options = {})
  246.     fu_check_options options, :noop, :verbose
  247.     list = fu_list(list)
  248.     fu_output_message "rmdir #{list.join ' '}" if options[:verbose]
  249.     return if options[:noop]
  250.     list.each do |dir|
  251.       Dir.rmdir dir.sub(%r</\z>, '')
  252.     end
  253.   end
  254.   module_function :rmdir
  255.  
  256.   OPT_TABLE['rmdir'] = %w( noop verbose )
  257.  
  258.   #
  259.   # Options: force noop verbose
  260.   #
  261.   # <b><tt>ln(old, new, options = {})</tt></b>
  262.   #
  263.   # Creates a hard link +new+ which points to +old+.
  264.   # If +new+ already exists and it is a directory, creates a link +new/old+.
  265.   # If +new+ already exists and it is not a directory, raises Errno::EEXIST.
  266.   # But if :force option is set, overwrite +new+.
  267.   # 
  268.   #   FileUtils.ln 'gcc', 'cc', :verbose => true
  269.   #   FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
  270.   # 
  271.   # <b><tt>ln(list, destdir, options = {})</tt></b>
  272.   # 
  273.   # Creates several hard links in a directory, with each one pointing to the
  274.   # item in +list+.  If +destdir+ is not a directory, raises Errno::ENOTDIR.
  275.   # 
  276.   #   include FileUtils
  277.   #   cd '/sbin'
  278.   #   FileUtils.ln %w(cp mv mkdir), '/bin'   # Now /sbin/cp and /bin/cp are linked.
  279.   # 
  280.   def ln(src, dest, options = {})
  281.     fu_check_options options, :force, :noop, :verbose
  282.     fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  283.     return if options[:noop]
  284.     fu_each_src_dest0(src, dest) do |s,d|
  285.       remove_file d, true if options[:force]
  286.       File.link s, d
  287.     end
  288.   end
  289.   module_function :ln
  290.  
  291.   alias link ln
  292.   module_function :link
  293.  
  294.   OPT_TABLE['ln']   =
  295.   OPT_TABLE['link'] = %w( noop verbose force )
  296.  
  297.   #
  298.   # Options: force noop verbose
  299.   #
  300.   # <b><tt>ln_s(old, new, options = {})</tt></b>
  301.   # 
  302.   # Creates a symbolic link +new+ which points to +old+.  If +new+ already
  303.   # exists and it is a directory, creates a symbolic link +new/old+.  If +new+
  304.   # already exists and it is not a directory, raises Errno::EEXIST.  But if
  305.   # :force option is set, overwrite +new+.
  306.   # 
  307.   #   FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
  308.   #   FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force => true
  309.   # 
  310.   # <b><tt>ln_s(list, destdir, options = {})</tt></b>
  311.   # 
  312.   # Creates several symbolic links in a directory, with each one pointing to the
  313.   # item in +list+.  If +destdir+ is not a directory, raises Errno::ENOTDIR.
  314.   #
  315.   # If +destdir+ is not a directory, raises Errno::ENOTDIR.
  316.   # 
  317.   #   FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin'
  318.   # 
  319.   def ln_s(src, dest, options = {})
  320.     fu_check_options options, :force, :noop, :verbose
  321.     fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  322.     return if options[:noop]
  323.     fu_each_src_dest0(src, dest) do |s,d|
  324.       remove_file d, true if options[:force]
  325.       File.symlink s, d
  326.     end
  327.   end
  328.   module_function :ln_s
  329.  
  330.   alias symlink ln_s
  331.   module_function :symlink
  332.  
  333.   OPT_TABLE['ln_s']    =
  334.   OPT_TABLE['symlink'] = %w( noop verbose force )
  335.  
  336.   #
  337.   # Options: noop verbose
  338.   # 
  339.   # Same as
  340.   #   #ln_s(src, dest, :force)
  341.   # 
  342.   def ln_sf(src, dest, options = {})
  343.     fu_check_options options, :noop, :verbose
  344.     options = options.dup
  345.     options[:force] = true
  346.     ln_s src, dest, options
  347.   end
  348.   module_function :ln_sf
  349.  
  350.   OPT_TABLE['ln_sf'] = %w( noop verbose )
  351.  
  352.   #
  353.   # Options: preserve noop verbose
  354.   #
  355.   # Copies a file content +src+ to +dest+.  If +dest+ is a directory,
  356.   # copies +src+ to +dest/src+.
  357.   #
  358.   # If +src+ is a list of files, then +dest+ must be a directory.
  359.   #
  360.   #   FileUtils.cp 'eval.c', 'eval.c.org'
  361.   #   FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
  362.   #   FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true
  363.   #   FileUtils.cp 'symlink', 'dest'   # copy content, "dest" is not a symlink
  364.   # 
  365.   def cp(src, dest, options = {})
  366.     fu_check_options options, :preserve, :noop, :verbose
  367.     fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  368.     return if options[:noop]
  369.     fu_each_src_dest(src, dest) do |s, d|
  370.       copy_file s, d, options[:preserve]
  371.     end
  372.   end
  373.   module_function :cp
  374.  
  375.   alias copy cp
  376.   module_function :copy
  377.  
  378.   OPT_TABLE['cp']   =
  379.   OPT_TABLE['copy'] = %w( noop verbose preserve )
  380.  
  381.   #
  382.   # Options: preserve noop verbose dereference_root
  383.   # 
  384.   # Copies +src+ to +dest+. If +src+ is a directory, this method copies
  385.   # all its contents recursively. If +dest+ is a directory, copies
  386.   # +src+ to +dest/src+.
  387.   #
  388.   # +src+ can be a list of files.
  389.   # 
  390.   #   # Installing ruby library "mylib" under the site_ruby
  391.   #   FileUtils.rm_r site_ruby + '/mylib', :force
  392.   #   FileUtils.cp_r 'lib/', site_ruby + '/mylib'
  393.   # 
  394.   #   # Examples of copying several files to target directory.
  395.   #   FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
  396.   #   FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop => true, :verbose => true
  397.   #
  398.   #   # If you want to copy all contents of a directory instead of the
  399.   #   # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
  400.   #   # use following code.
  401.   #   FileUtils.cp_r 'src/.', 'dest'     # cp_r('src', 'dest') makes src/dest,
  402.   #                                      # but this doesn't.
  403.   # 
  404.   def cp_r(src, dest, options = {})
  405.     fu_check_options options, :preserve, :noop, :verbose, :dereference_root
  406.     fu_output_message "cp -r#{options[:preserve] ? 'p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  407.     return if options[:noop]
  408.     options[:dereference_root] = true unless options.key?(:dereference_root)
  409.     fu_each_src_dest(src, dest) do |s, d|
  410.       copy_entry s, d, options[:preserve], options[:dereference_root]
  411.     end
  412.   end
  413.   module_function :cp_r
  414.  
  415.   OPT_TABLE['cp_r'] = %w( noop verbose preserve dereference_root )
  416.  
  417.   #
  418.   # Copies a file system entry +src+ to +dest+.
  419.   # If +src+ is a directory, this method copies its contents recursively.
  420.   # This method preserves file types, c.f. symlink, directory...
  421.   # (FIFO, device files and etc. are not supported yet)
  422.   #
  423.   # Both of +src+ and +dest+ must be a path name.
  424.   # +src+ must exist, +dest+ must not exist.
  425.   #
  426.   # If +preserve+ is true, this method preserves owner, group, permissions
  427.   # and modified time.
  428.   #
  429.   # If +dereference_root+ is true, this method dereference tree root.
  430.   #
  431.   def copy_entry(src, dest, preserve = false, dereference_root = false)
  432.     Entry_.new(src, nil, dereference_root).traverse do |ent|
  433.       destent = Entry_.new(dest, ent.rel, false)
  434.       ent.copy destent.path
  435.       ent.copy_metadata destent.path if preserve
  436.     end
  437.   end
  438.   module_function :copy_entry
  439.  
  440.   #
  441.   # Copies file contents of +src+ to +dest+.
  442.   # Both of +src+ and +dest+ must be a path name.
  443.   #
  444.   def copy_file(src, dest, preserve = false, dereference = true)
  445.     ent = Entry_.new(src, nil, dereference)
  446.     ent.copy_file dest
  447.     ent.copy_metadata dest if preserve
  448.   end
  449.   module_function :copy_file
  450.  
  451.   #
  452.   # Copies stream +src+ to +dest+.
  453.   # +src+ must respond to #read(n) and
  454.   # +dest+ must respond to #write(str).
  455.   #
  456.   def copy_stream(src, dest)
  457.     fu_copy_stream0 src, dest, fu_stream_blksize(src, dest)
  458.   end
  459.   module_function :copy_stream
  460.  
  461.   #
  462.   # Options: force noop verbose
  463.   # 
  464.   # Moves file(s) +src+ to +dest+.  If +file+ and +dest+ exist on the different
  465.   # disk partition, the file is copied instead.
  466.   # 
  467.   #   FileUtils.mv 'badname.rb', 'goodname.rb'
  468.   #   FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true  # no error
  469.   # 
  470.   #   FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/'
  471.   #   FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
  472.   # 
  473.   def mv(src, dest, options = {})
  474.     fu_check_options options, :force, :noop, :verbose
  475.     fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  476.     return if options[:noop]
  477.     fu_each_src_dest(src, dest) do |s, d|
  478.       destent = Entry_.new(d, nil, true)
  479.       begin
  480.         if destent.exist?
  481.           if destent.directory?
  482.             raise Errno::EEXIST, dest
  483.           else
  484.             destent.remove_file if rename_cannot_overwrite_file?
  485.           end
  486.         end
  487.         begin
  488.           File.rename s, d
  489.         rescue Errno::EXDEV
  490.           copy_entry s, d, true
  491.           File.unlink s
  492.         end
  493.       rescue SystemCallError
  494.         raise unless options[:force]
  495.       end
  496.     end
  497.   end
  498.   module_function :mv
  499.  
  500.   alias move mv
  501.   module_function :move
  502.  
  503.   OPT_TABLE['mv']   =
  504.   OPT_TABLE['move'] = %w( noop verbose force )
  505.  
  506.   def rename_cannot_overwrite_file?   #:nodoc:
  507.     /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
  508.   end
  509.   private_module_function :rename_cannot_overwrite_file?
  510.  
  511.   #
  512.   # Options: force noop verbose
  513.   # 
  514.   # Remove file(s) specified in +list+.  This method cannot remove directories.
  515.   # All StandardErrors are ignored when the :force option is set.
  516.   # 
  517.   #   FileUtils.rm %w( junk.txt dust.txt )
  518.   #   FileUtils.rm Dir.glob('*.so')
  519.   #   FileUtils.rm 'NotExistFile', :force => true   # never raises exception
  520.   # 
  521.   def rm(list, options = {})
  522.     fu_check_options options, :force, :noop, :verbose
  523.     list = fu_list(list)
  524.     fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose]
  525.     return if options[:noop]
  526.  
  527.     list.each do |path|
  528.       remove_file path, options[:force]
  529.     end
  530.   end
  531.   module_function :rm
  532.  
  533.   alias remove rm
  534.   module_function :remove
  535.  
  536.   OPT_TABLE['rm']     =
  537.   OPT_TABLE['remove'] = %w( noop verbose force )
  538.  
  539.   #
  540.   # Options: noop verbose
  541.   # 
  542.   # Equivalent to
  543.   #
  544.   #   #rm(list, :force => true)
  545.   #
  546.   def rm_f(list, options = {})
  547.     fu_check_options options, :noop, :verbose
  548.     options = options.dup
  549.     options[:force] = true
  550.     rm list, options
  551.   end
  552.   module_function :rm_f
  553.  
  554.   alias safe_unlink rm_f
  555.   module_function :safe_unlink
  556.  
  557.   OPT_TABLE['rm_f']        =
  558.   OPT_TABLE['safe_unlink'] = %w( noop verbose )
  559.  
  560.   #
  561.   # Options: force noop verbose secure
  562.   # 
  563.   # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
  564.   # removes its all contents recursively. This method ignores
  565.   # StandardError when :force option is set.
  566.   # 
  567.   #   FileUtils.rm_r Dir.glob('/tmp/*')
  568.   #   FileUtils.rm_r '/', :force => true          #  :-)
  569.   #
  570.   # WARNING: This method causes local vulnerability
  571.   # if one of parent directories or removing directory tree are world
  572.   # writable (including /tmp, whose permission is 1777), and the current
  573.   # process has strong privilege such as Unix super user (root), and the
  574.   # system has symbolic link.  For secure removing, read the documentation
  575.   # of #remove_entry_secure carefully, and set :secure option to true.
  576.   # Default is :secure=>false.
  577.   #
  578.   # NOTE: This method calls #remove_entry_secure if :secure option is set.
  579.   # See also #remove_entry_secure.
  580.   # 
  581.   def rm_r(list, options = {})
  582.     fu_check_options options, :force, :noop, :verbose, :secure
  583.     # options[:secure] = true unless options.key?(:secure)
  584.     list = fu_list(list)
  585.     fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose]
  586.     return if options[:noop]
  587.     list.each do |path|
  588.       if options[:secure]
  589.         remove_entry_secure path, options[:force]
  590.       else
  591.         remove_entry path, options[:force]
  592.       end
  593.     end
  594.   end
  595.   module_function :rm_r
  596.  
  597.   OPT_TABLE['rm_r'] = %w( noop verbose force secure )
  598.  
  599.   #
  600.   # Options: noop verbose secure
  601.   # 
  602.   # Equivalent to
  603.   #
  604.   #   #rm_r(list, :force => true)
  605.   #
  606.   # WARNING: This method causes local vulnerability.
  607.   # Read the documentation of #rm_r first.
  608.   # 
  609.   def rm_rf(list, options = {})
  610.     fu_check_options options, :noop, :verbose, :secure
  611.     options = options.dup
  612.     options[:force] = true
  613.     rm_r list, options
  614.   end
  615.   module_function :rm_rf
  616.  
  617.   alias rmtree rm_rf
  618.   module_function :rmtree
  619.  
  620.   OPT_TABLE['rm_rf']  =
  621.   OPT_TABLE['rmtree'] = %w( noop verbose secure )
  622.  
  623.   #
  624.   # This method removes a file system entry +path+.  +path+ shall be a
  625.   # regular file, a directory, or something.  If +path+ is a directory,
  626.   # remove it recursively.  This method is required to avoid TOCTTOU
  627.   # (time-of-check-to-time-of-use) local security vulnerability of #rm_r.
  628.   # #rm_r causes security hole when:
  629.   #
  630.   #   * Parent directory is world writable (including /tmp).
  631.   #   * Removing directory tree includes world writable directory.
  632.   #   * The system has symbolic link.
  633.   #
  634.   # To avoid this security hole, this method applies special preprocess.
  635.   # If +path+ is a directory, this method chown(2) and chmod(2) all
  636.   # removing directories.  This requires the current process is the
  637.   # owner of the removing whole directory tree, or is the super user (root).
  638.   #
  639.   # WARNING: You must ensure that *ALL* parent directories are not
  640.   # world writable.  Otherwise this method does not work.
  641.   # Only exception is temporary directory like /tmp and /var/tmp,
  642.   # whose permission is 1777.
  643.   #
  644.   # WARNING: Only the owner of the removing directory tree, or Unix super
  645.   # user (root) should invoke this method.  Otherwise this method does not
  646.   # work.
  647.   #
  648.   # For details of this security vulnerability, see Perl's case:
  649.   #
  650.   #   http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
  651.   #   http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
  652.   #
  653.   # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
  654.   #
  655.   def remove_entry_secure(path, force = false)
  656.     unless fu_have_symlink?
  657.       remove_entry path, force
  658.       return
  659.     end
  660.     fullpath = File.expand_path(path)
  661.     st = File.lstat(fullpath)
  662.     unless st.directory?
  663.       File.unlink fullpath
  664.       return
  665.     end
  666.     # is a directory.
  667.     parent_st = File.stat(File.dirname(fullpath))
  668.     unless fu_world_writable?(parent_st)
  669.       remove_entry path, force
  670.       return
  671.     end
  672.     unless parent_st.sticky?
  673.       raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
  674.     end
  675.     # freeze tree root
  676.     euid = Process.euid
  677.     File.open(fullpath + '/.') {|f|
  678.       unless fu_stat_identical_entry?(st, f.stat)
  679.         # symlink (TOC-to-TOU attack?)
  680.         File.unlink fullpath
  681.         return
  682.       end
  683.       f.chown euid, -1
  684.       f.chmod 0700
  685.     }
  686.     # ---- tree root is frozen ----
  687.     root = Entry_.new(path)
  688.     root.preorder_traverse do |ent|
  689.       if ent.directory?
  690.         ent.chown euid, -1
  691.         ent.chmod 0700
  692.       end
  693.     end
  694.     root.postorder_traverse do |ent|
  695.       begin
  696.         ent.remove
  697.       rescue
  698.         raise unless force
  699.       end
  700.     end
  701.   rescue
  702.     raise unless force
  703.   end
  704.   module_function :remove_entry_secure
  705.  
  706.   def fu_world_writable?(st)
  707.     (st.mode & 0002) != 0
  708.   end
  709.   private_module_function :fu_world_writable?
  710.  
  711.   def fu_have_symlink?   #:nodoc
  712.     File.symlink nil, nil
  713.   rescue NotImplementedError
  714.     return false
  715.   rescue
  716.     return true
  717.   end
  718.   private_module_function :fu_have_symlink?
  719.  
  720.   def fu_stat_identical_entry?(a, b)   #:nodoc:
  721.     a.dev == b.dev and a.ino == b.ino
  722.   end
  723.   private_module_function :fu_stat_identical_entry?
  724.  
  725.   #
  726.   # This method removes a file system entry +path+.
  727.   # +path+ might be a regular file, a directory, or something.
  728.   # If +path+ is a directory, remove it recursively.
  729.   #
  730.   # See also #remove_entry_secure.
  731.   #
  732.   def remove_entry(path, force = false)
  733.     Entry_.new(path).postorder_traverse do |ent|
  734.       begin
  735.         ent.remove
  736.       rescue
  737.         raise unless force
  738.       end
  739.     end
  740.   rescue
  741.     raise unless force
  742.   end
  743.   module_function :remove_entry
  744.  
  745.   #
  746.   # Removes a file +path+.
  747.   # This method ignores StandardError if +force+ is true.
  748.   #
  749.   def remove_file(path, force = false)
  750.     Entry_.new(path).remove_file
  751.   rescue
  752.     raise unless force
  753.   end
  754.   module_function :remove_file
  755.  
  756.   #
  757.   # Removes a directory +dir+ and its contents recursively.
  758.   # This method ignores StandardError if +force+ is true.
  759.   #
  760.   def remove_dir(path, force = false)
  761.     remove_entry path, force   # FIXME?? check if it is a directory
  762.   end
  763.   module_function :remove_dir
  764.  
  765.   #
  766.   # Returns true if the contents of a file A and a file B are identical.
  767.   # 
  768.   #   FileUtils.compare_file('somefile', 'somefile')  #=> true
  769.   #   FileUtils.compare_file('/bin/cp', '/bin/mv')    #=> maybe false
  770.   #
  771.   def compare_file(a, b)
  772.     return false unless File.size(a) == File.size(b)
  773.     File.open(a, 'rb') {|fa|
  774.       File.open(b, 'rb') {|fb|
  775.         return compare_stream(fa, fb)
  776.       }
  777.     }
  778.   end
  779.   module_function :compare_file
  780.  
  781.   alias identical? compare_file
  782.   alias cmp compare_file
  783.   module_function :identical?
  784.   module_function :cmp
  785.  
  786.   #
  787.   # Returns true if the contents of a stream +a+ and +b+ are identical.
  788.   #
  789.   def compare_stream(a, b)
  790.     bsize = fu_stream_blksize(a, b)
  791.     sa = sb = nil
  792.     while sa == sb
  793.       sa = a.read(bsize)
  794.       sb = b.read(bsize)
  795.       unless sa and sb
  796.         if sa.nil? and sb.nil?
  797.           return true
  798.         end
  799.       end
  800.     end
  801.     false
  802.   end
  803.   module_function :compare_stream
  804.  
  805.   #
  806.   # Options: mode noop verbose
  807.   # 
  808.   # If +src+ is not same as +dest+, copies it and changes the permission
  809.   # mode to +mode+.  If +dest+ is a directory, destination is +dest+/+src+.
  810.   # 
  811.   #   FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true
  812.   #   FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true
  813.   # 
  814.   def install(src, dest, options = {})
  815.     fu_check_options options, :mode, :preserve, :noop, :verbose
  816.     fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  817.     return if options[:noop]
  818.     fu_each_src_dest(src, dest) do |s, d|
  819.       unless File.exist?(d) and compare_file(s, d)
  820.         remove_file d, true
  821.         st = File.stat(s) if options[:preserve]
  822.         copy_file s, d
  823.         File.utime st.atime, st.mtime, d if options[:preserve]
  824.         File.chmod options[:mode], d if options[:mode]
  825.       end
  826.     end
  827.   end
  828.   module_function :install
  829.  
  830.   OPT_TABLE['install'] = %w( noop verbose preserve mode )
  831.  
  832.   #
  833.   # Options: noop verbose
  834.   # 
  835.   # Changes permission bits on the named files (in +list+) to the bit pattern
  836.   # represented by +mode+.
  837.   # 
  838.   #   FileUtils.chmod 0755, 'somecommand'
  839.   #   FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
  840.   #   FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true
  841.   # 
  842.   def chmod(mode, list, options = {})
  843.     fu_check_options options, :noop, :verbose
  844.     list = fu_list(list)
  845.     fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if options[:verbose]
  846.     return if options[:noop]
  847.     list.each do |path|
  848.       Entry_.new(path).chmod mode
  849.     end
  850.   end
  851.   module_function :chmod
  852.  
  853.   OPT_TABLE['chmod'] = %w( noop verbose )
  854.  
  855.   #
  856.   # Options: noop verbose force
  857.   # 
  858.   # Changes permission bits on the named files (in +list+)
  859.   # to the bit pattern represented by +mode+.
  860.   # 
  861.   #   FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
  862.   # 
  863.   def chmod_R(mode, list, options = {})
  864.     fu_check_options options, :noop, :verbose, :force
  865.     list = fu_list(list)
  866.     fu_output_message sprintf('chmod -R%s %o %s',
  867.                               (options[:force] ? 'f' : ''),
  868.                               mode, list.join(' ')) if options[:verbose]
  869.     return if options[:noop]
  870.     list.each do |root|
  871.       Entry_.new(root).traverse do |ent|
  872.         begin
  873.           ent.chmod mode
  874.         rescue
  875.           raise unless options[:force]
  876.         end
  877.       end
  878.     end
  879.   end
  880.   module_function :chmod_R
  881.  
  882.   OPT_TABLE['chmod_R'] = %w( noop verbose )
  883.  
  884.   #
  885.   # Options: noop verbose
  886.   # 
  887.   # Changes owner and group on the named files (in +list+)
  888.   # to the user +user+ and the group +group+.  +user+ and +group+
  889.   # may be an ID (Integer/String) or a name (String).
  890.   # If +user+ or +group+ is nil, this method does not change
  891.   # the attribute.
  892.   # 
  893.   #   FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
  894.   #   FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true
  895.   # 
  896.   def chown(user, group, list, options = {})
  897.     fu_check_options options, :noop, :verbose
  898.     list = fu_list(list)
  899.     fu_output_message sprintf('chown %s%s',
  900.                               [user,group].compact.join(':') + ' ',
  901.                               list.join(' ')) if options[:verbose]
  902.     return if options[:noop]
  903.     uid = fu_get_uid(user)
  904.     gid = fu_get_gid(group)
  905.     list.each do |path|
  906.       Entry_.new(path).chown uid, gid
  907.     end
  908.   end
  909.   module_function :chown
  910.  
  911.   OPT_TABLE['chown'] = %w( noop verbose )
  912.  
  913.   #
  914.   # Options: noop verbose force
  915.   # 
  916.   # Changes owner and group on the named files (in +list+)
  917.   # to the user +user+ and the group +group+ recursively.
  918.   # +user+ and +group+ may be an ID (Integer/String) or
  919.   # a name (String).  If +user+ or +group+ is nil, this
  920.   # method does not change the attribute.
  921.   # 
  922.   #   FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
  923.   #   FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true
  924.   # 
  925.   def chown_R(user, group, list, options = {})
  926.     fu_check_options options, :noop, :verbose, :force
  927.     list = fu_list(list)
  928.     fu_output_message sprintf('chown -R%s %s%s',
  929.                               (options[:force] ? 'f' : ''),
  930.                               [user,group].compact.join(':') + ' ',
  931.                               list.join(' ')) if options[:verbose]
  932.     return if options[:noop]
  933.     uid = fu_get_uid(user)
  934.     gid = fu_get_gid(group)
  935.     return unless uid or gid
  936.     list.each do |root|
  937.       Entry_.new(root).traverse do |ent|
  938.         begin
  939.           ent.chown uid, gid
  940.         rescue
  941.           raise unless options[:force]
  942.         end
  943.       end
  944.     end
  945.   end
  946.   module_function :chown_R
  947.  
  948.   OPT_TABLE['chown_R'] = %w( noop verbose )
  949.  
  950.   begin
  951.     require 'etc'
  952.  
  953.     def fu_get_uid(user)   #:nodoc:
  954.       return nil unless user
  955.       user = user.to_s
  956.       if /\A\d+\z/ =~ user
  957.       then user.to_i
  958.       else Etc.getpwnam(user).uid
  959.       end
  960.     end
  961.     private_module_function :fu_get_uid
  962.  
  963.     def fu_get_gid(group)   #:nodoc:
  964.       return nil unless group
  965.       if /\A\d+\z/ =~ group
  966.       then group.to_i
  967.       else Etc.getgrnam(group).gid
  968.       end
  969.     end
  970.     private_module_function :fu_get_gid
  971.  
  972.   rescue LoadError
  973.     # need Win32 support???
  974.  
  975.     def fu_get_uid(user)   #:nodoc:
  976.       user    # FIXME
  977.     end
  978.     private_module_function :fu_get_uid
  979.  
  980.     def fu_get_gid(group)   #:nodoc:
  981.       group   # FIXME
  982.     end
  983.     private_module_function :fu_get_gid
  984.   end
  985.  
  986.   #
  987.   # Options: noop verbose
  988.   # 
  989.   # Updates modification time (mtime) and access time (atime) of file(s) in
  990.   # +list+.  Files are created if they don't exist.
  991.   # 
  992.   #   FileUtils.touch 'timestamp'
  993.   #   FileUtils.touch Dir.glob('*.c');  system 'make'
  994.   # 
  995.   def touch(list, options = {})
  996.     fu_check_options options, :noop, :verbose
  997.     list = fu_list(list)
  998.     fu_output_message "touch #{list.join ' '}" if options[:verbose]
  999.     return if options[:noop]
  1000.     t = Time.now
  1001.     list.each do |path|
  1002.       begin
  1003.         File.utime(t, t, path)
  1004.       rescue Errno::ENOENT
  1005.         File.open(path, 'a') {
  1006.           ;
  1007.         }
  1008.       end
  1009.     end
  1010.   end
  1011.   module_function :touch
  1012.  
  1013.   OPT_TABLE['touch'] = %w( noop verbose )
  1014.  
  1015.   private
  1016.  
  1017.   module StreamUtils_
  1018.     private
  1019.  
  1020.     def fu_windows?
  1021.       /mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
  1022.     end
  1023.  
  1024.     def fu_copy_stream0(src, dest, blksize)   #:nodoc:
  1025.       # FIXME: readpartial?
  1026.       while s = src.read(blksize)
  1027.         dest.write s
  1028.       end
  1029.     end
  1030.  
  1031.     def fu_stream_blksize(*streams)
  1032.       streams.each do |s|
  1033.         next unless s.respond_to?(:stat)
  1034.         size = fu_blksize(s.stat)
  1035.         return size if size
  1036.       end
  1037.       fu_default_blksize()
  1038.     end
  1039.  
  1040.     def fu_blksize(st)
  1041.       s = st.blksize
  1042.       return nil unless s
  1043.       return nil if s == 0
  1044.       s
  1045.     end
  1046.  
  1047.     def fu_default_blksize
  1048.       1024
  1049.     end
  1050.   end
  1051.  
  1052.   include StreamUtils_
  1053.   extend StreamUtils_
  1054.  
  1055.   class Entry_   #:nodoc: internal use only
  1056.     include StreamUtils_
  1057.  
  1058.     def initialize(a, b = nil, deref = false)
  1059.       @prefix = @rel = @path = nil
  1060.       if b
  1061.         @prefix = a
  1062.         @rel = b
  1063.       else
  1064.         @path = a
  1065.       end
  1066.       @deref = deref
  1067.       @stat = nil
  1068.       @lstat = nil
  1069.     end
  1070.  
  1071.     def inspect
  1072.       "\#<#{self.class} #{path()}>"
  1073.     end
  1074.  
  1075.     def path
  1076.       if @path
  1077.         @path.to_str
  1078.       else
  1079.         join(@prefix, @rel)
  1080.       end
  1081.     end
  1082.  
  1083.     def prefix
  1084.       @prefix || @path
  1085.     end
  1086.  
  1087.     def rel
  1088.       @rel
  1089.     end
  1090.  
  1091.     def dereference?
  1092.       @deref
  1093.     end
  1094.  
  1095.     def exist?
  1096.       lstat! ? true : false
  1097.     end
  1098.  
  1099.     def file?
  1100.       s = lstat!
  1101.       s and s.file?
  1102.     end
  1103.  
  1104.     def directory?
  1105.       s = lstat!
  1106.       s and s.directory?
  1107.     end
  1108.  
  1109.     def symlink?
  1110.       s = lstat!
  1111.       s and s.symlink?
  1112.     end
  1113.  
  1114.     def chardev?
  1115.       s = lstat!
  1116.       s and s.chardev?
  1117.     end
  1118.  
  1119.     def blockdev?
  1120.       s = lstat!
  1121.       s and s.blockdev?
  1122.     end
  1123.  
  1124.     def socket?
  1125.       s = lstat!
  1126.       s and s.socket?
  1127.     end
  1128.  
  1129.     def pipe?
  1130.       s = lstat!
  1131.       s and s.pipe?
  1132.     end
  1133.  
  1134.     S_IF_DOOR = 0xD000
  1135.  
  1136.     def door?
  1137.       s = lstat!
  1138.       s and (s.mode & 0xF000 == S_IF_DOOR)
  1139.     end
  1140.  
  1141.     def entries
  1142.       Dir.entries(path())\
  1143.           .reject {|n| n == '.' or n == '..' }\
  1144.           .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
  1145.     end
  1146.  
  1147.     def stat
  1148.       return @stat if @stat
  1149.       if lstat() and lstat().symlink?
  1150.         @stat = File.stat(path())
  1151.       else
  1152.         @stat = lstat()
  1153.       end
  1154.       @stat
  1155.     end
  1156.  
  1157.     def stat!
  1158.       return @stat if @stat
  1159.       if lstat! and lstat!.symlink?
  1160.         @stat = File.stat(path())
  1161.       else
  1162.         @stat = lstat!
  1163.       end
  1164.       @stat
  1165.     rescue SystemCallError
  1166.       nil
  1167.     end
  1168.  
  1169.     def lstat
  1170.       if dereference?
  1171.         @lstat ||= File.stat(path())
  1172.       else
  1173.         @lstat ||= File.lstat(path())
  1174.       end
  1175.     end
  1176.  
  1177.     def lstat!
  1178.       lstat()
  1179.     rescue SystemCallError
  1180.       nil
  1181.     end
  1182.  
  1183.     def chmod(mode)
  1184.       if symlink?
  1185.         File.lchmod mode, path() if have_lchmod?
  1186.       else
  1187.         File.chmod mode, path()
  1188.       end
  1189.     end
  1190.  
  1191.     def chown(uid, gid)
  1192.       if symlink?
  1193.         File.lchown uid, gid, path() if have_lchown?
  1194.       else
  1195.         File.chown uid, gid, path()
  1196.       end
  1197.     end
  1198.  
  1199.     def copy(dest)
  1200.       case
  1201.       when file?
  1202.         copy_file dest
  1203.       when directory?
  1204.         begin
  1205.           Dir.mkdir dest
  1206.         rescue
  1207.           raise unless File.directory?(dest)
  1208.         end
  1209.       when symlink?
  1210.         File.symlink File.readlink(path()), dest
  1211.       when chardev?
  1212.         raise "cannot handle device file" unless File.respond_to?(:mknod)
  1213.         mknod dest, ?c, 0666, lstat().rdev
  1214.       when blockdev?
  1215.         raise "cannot handle device file" unless File.respond_to?(:mknod)
  1216.         mknod dest, ?b, 0666, lstat().rdev
  1217.       when socket?
  1218.         raise "cannot handle socket" unless File.respond_to?(:mknod)
  1219.         mknod dest, nil, lstat().mode, 0
  1220.       when pipe?
  1221.         raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
  1222.         mkfifo dest, 0666
  1223.       when door?
  1224.         raise "cannot handle door: #{path()}"
  1225.       else
  1226.         raise "unknown file type: #{path()}"
  1227.       end
  1228.     end
  1229.  
  1230.     def copy_file(dest)
  1231.       st = stat()
  1232.       File.open(path(),  'rb') {|r|
  1233.         File.open(dest, 'wb', st.mode) {|w|
  1234.           fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize())
  1235.         }
  1236.       }
  1237.     end
  1238.  
  1239.     def copy_metadata(path)
  1240.       st = lstat()
  1241.       File.utime st.atime, st.mtime, path
  1242.       begin
  1243.         File.chown st.uid, st.gid, path
  1244.       rescue Errno::EPERM
  1245.         # clear setuid/setgid
  1246.         File.chmod st.mode & 01777, path
  1247.       else
  1248.         File.chmod st.mode, path
  1249.       end
  1250.     end
  1251.  
  1252.     def remove
  1253.       if directory?
  1254.         remove_dir1
  1255.       else
  1256.         remove_file
  1257.       end
  1258.     end
  1259.  
  1260.     def remove_dir1
  1261.       platform_support {
  1262.         Dir.rmdir path().sub(%r</\z>, '')
  1263.       }
  1264.     end
  1265.  
  1266.     def remove_file
  1267.       platform_support {
  1268.         File.unlink path
  1269.       }
  1270.     end
  1271.  
  1272.     def platform_support
  1273.       return yield unless fu_windows?
  1274.       first_time_p = true
  1275.       begin
  1276.         yield
  1277.       rescue Errno::ENOENT
  1278.         raise
  1279.       rescue => err
  1280.         if first_time_p
  1281.           first_time_p = false
  1282.           begin
  1283.             File.chmod 0700, path()   # Windows does not have symlink
  1284.             retry
  1285.           rescue SystemCallError
  1286.           end
  1287.         end
  1288.         raise err
  1289.       end
  1290.     end
  1291.  
  1292.     def preorder_traverse
  1293.       stack = [self]
  1294.       while ent = stack.pop
  1295.         yield ent
  1296.         stack.concat ent.entries.reverse if ent.directory?
  1297.       end
  1298.     end
  1299.  
  1300.     alias traverse preorder_traverse
  1301.  
  1302.     def postorder_traverse
  1303.       if directory?
  1304.         entries().each do |ent|
  1305.           ent.postorder_traverse do |e|
  1306.             yield e
  1307.           end
  1308.         end
  1309.       end
  1310.       yield self
  1311.     end
  1312.  
  1313.     private
  1314.  
  1315.     $fileutils_rb_have_lchmod = nil
  1316.  
  1317.     def have_lchmod?
  1318.       # This is not MT-safe, but it does not matter.
  1319.       if $fileutils_rb_have_lchmod == nil
  1320.         $fileutils_rb_have_lchmod = check_have_lchmod?
  1321.       end
  1322.       $fileutils_rb_have_lchmod
  1323.     end
  1324.  
  1325.     def check_have_lchmod?
  1326.       return false unless File.respond_to?(:lchmod)
  1327.       File.lchmod 0
  1328.       return true
  1329.     rescue NotImplementedError
  1330.       return false
  1331.     end
  1332.  
  1333.     $fileutils_rb_have_lchown = nil
  1334.  
  1335.     def have_lchown?
  1336.       # This is not MT-safe, but it does not matter.
  1337.       if $fileutils_rb_have_lchown == nil
  1338.         $fileutils_rb_have_lchown = check_have_lchown?
  1339.       end
  1340.       $fileutils_rb_have_lchown
  1341.     end
  1342.  
  1343.     def check_have_lchown?
  1344.       return false unless File.respond_to?(:lchown)
  1345.       File.lchown nil, nil
  1346.       return true
  1347.     rescue NotImplementedError
  1348.       return false
  1349.     end
  1350.  
  1351.     def join(dir, base)
  1352.       return dir.to_str if not base or base == '.'
  1353.       return base.to_str if not dir or dir == '.'
  1354.       File.join(dir, base)
  1355.     end
  1356.   end   # class Entry_
  1357.  
  1358.   def fu_list(arg)   #:nodoc:
  1359.     [arg].flatten.map {|path| path.to_str }
  1360.   end
  1361.   private_module_function :fu_list
  1362.  
  1363.   def fu_each_src_dest(src, dest)   #:nodoc:
  1364.     fu_each_src_dest0(src, dest) do |s, d|
  1365.       raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
  1366.       yield s, d
  1367.     end
  1368.   end
  1369.   private_module_function :fu_each_src_dest
  1370.  
  1371.   def fu_each_src_dest0(src, dest)   #:nodoc:
  1372.     if src.is_a?(Array)
  1373.       src.each do |s|
  1374.         s = s.to_str
  1375.         yield s, File.join(dest, File.basename(s))
  1376.       end
  1377.     else
  1378.       src = src.to_str
  1379.       if File.directory?(dest)
  1380.         yield src, File.join(dest, File.basename(src))
  1381.       else
  1382.         yield src, dest.to_str
  1383.       end
  1384.     end
  1385.   end
  1386.   private_module_function :fu_each_src_dest0
  1387.  
  1388.   def fu_same?(a, b)   #:nodoc:
  1389.     if fu_have_st_ino?
  1390.       st1 = File.stat(a)
  1391.       st2 = File.stat(b)
  1392.       st1.dev == st2.dev and st1.ino == st2.ino
  1393.     else
  1394.       File.expand_path(a) == File.expand_path(b)
  1395.     end
  1396.   rescue Errno::ENOENT
  1397.     return false
  1398.   end
  1399.   private_module_function :fu_same?
  1400.  
  1401.   def fu_have_st_ino?   #:nodoc:
  1402.     not fu_windows?
  1403.   end
  1404.   private_module_function :fu_have_st_ino?
  1405.  
  1406.   def fu_check_options(options, *optdecl)   #:nodoc:
  1407.     h = options.dup
  1408.     optdecl.each do |name|
  1409.       h.delete name
  1410.     end
  1411.     raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty?
  1412.   end
  1413.   private_module_function :fu_check_options
  1414.  
  1415.   def fu_update_option(args, new)   #:nodoc:
  1416.     if args.last.is_a?(Hash)
  1417.       args[-1] = args.last.dup.update(new)
  1418.     else
  1419.       args.push new
  1420.     end
  1421.     args
  1422.   end
  1423.   private_module_function :fu_update_option
  1424.  
  1425.   @fileutils_output = $stderr
  1426.   @fileutils_label  = ''
  1427.  
  1428.   def fu_output_message(msg)   #:nodoc:
  1429.     @fileutils_output ||= $stderr
  1430.     @fileutils_label  ||= ''
  1431.     @fileutils_output.puts @fileutils_label + msg
  1432.   end
  1433.   private_module_function :fu_output_message
  1434.  
  1435.   METHODS = singleton_methods() - ['private_module_function']
  1436.  
  1437.   #
  1438.   # Returns an Array of method names which have any options.
  1439.   #
  1440.   #   p FileUtils.commands  #=> ["chmod", "cp", "cp_r", "install", ...]
  1441.   #
  1442.   def FileUtils.commands
  1443.     OPT_TABLE.keys
  1444.   end
  1445.  
  1446.   #
  1447.   # Returns an Array of option names.
  1448.   #
  1449.   #   p FileUtils.options  #=> ["noop", "force", "verbose", "preserve", "mode"]
  1450.   #
  1451.   def FileUtils.options
  1452.     OPT_TABLE.values.flatten.uniq
  1453.   end
  1454.  
  1455.   #
  1456.   # Returns true if the method +mid+ have an option +opt+.
  1457.   #
  1458.   #   p FileUtils.have_option?(:cp, :noop)     #=> true
  1459.   #   p FileUtils.have_option?(:rm, :force)    #=> true
  1460.   #   p FileUtils.have_option?(:rm, :perserve) #=> false
  1461.   #
  1462.   def FileUtils.have_option?(mid, opt)
  1463.     li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
  1464.     li.include?(opt.to_s)
  1465.   end
  1466.  
  1467.   #
  1468.   # Returns an Array of option names of the method +mid+.
  1469.   #
  1470.   #   p FileUtils.options(:rm)  #=> ["noop", "verbose", "force"]
  1471.   #
  1472.   def FileUtils.options_of(mid)
  1473.     OPT_TABLE[mid.to_s]
  1474.   end
  1475.  
  1476.   #
  1477.   # Returns an Array of method names which have the option +opt+.
  1478.   #
  1479.   #   p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
  1480.   #
  1481.   def FileUtils.collect_method(opt)
  1482.     OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt.to_s) }
  1483.   end
  1484.  
  1485.   # 
  1486.   # This module has all methods of FileUtils module, but it outputs messages
  1487.   # before acting.  This equates to passing the <tt>:verbose</tt> flag to
  1488.   # methods in FileUtils.
  1489.   # 
  1490.   module Verbose
  1491.     include FileUtils
  1492.     @fileutils_output  = $stderr
  1493.     @fileutils_label   = ''
  1494.     ::FileUtils.collect_method('verbose').each do |name|
  1495.       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
  1496.         def #{name}(*args)
  1497.           super(*fu_update_option(args, :verbose => true))
  1498.         end
  1499.         private :#{name}
  1500.       EOS
  1501.     end
  1502.     extend self
  1503.     class << self
  1504.       ::FileUtils::METHODS.each do |m|
  1505.         public m
  1506.       end
  1507.     end
  1508.   end
  1509.  
  1510.   # 
  1511.   # This module has all methods of FileUtils module, but never changes
  1512.   # files/directories.  This equates to passing the <tt>:noop</tt> flag
  1513.   # to methods in FileUtils.
  1514.   # 
  1515.   module NoWrite
  1516.     include FileUtils
  1517.     @fileutils_output  = $stderr
  1518.     @fileutils_label   = ''
  1519.     ::FileUtils.collect_method('noop').each do |name|
  1520.       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
  1521.         def #{name}(*args)
  1522.           super(*fu_update_option(args, :noop => true))
  1523.         end
  1524.         private :#{name}
  1525.       EOS
  1526.     end
  1527.     extend self
  1528.     class << self
  1529.       ::FileUtils::METHODS.each do |m|
  1530.         public m
  1531.       end
  1532.     end
  1533.   end
  1534.  
  1535.   # 
  1536.   # This module has all methods of FileUtils module, but never changes
  1537.   # files/directories, with printing message before acting.
  1538.   # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
  1539.   # to methods in FileUtils.
  1540.   # 
  1541.   module DryRun
  1542.     include FileUtils
  1543.     @fileutils_output  = $stderr
  1544.     @fileutils_label   = ''
  1545.     ::FileUtils.collect_method('noop').each do |name|
  1546.       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
  1547.         def #{name}(*args)
  1548.           super(*fu_update_option(args, :noop => true, :verbose => true))
  1549.         end
  1550.         private :#{name}
  1551.       EOS
  1552.     end
  1553.     extend self
  1554.     class << self
  1555.       ::FileUtils::METHODS.each do |m|
  1556.         public m
  1557.       end
  1558.     end
  1559.   end
  1560.  
  1561. end
  1562.